home *** CD-ROM | disk | FTP | other *** search
/ Delphi Magazine Collection 2001 / Delphi Magazine Collection 20001 (2001).iso / DISKS / Issue67 / Alfresco / AALCS.pas next >
Encoding:
Pascal/Delphi Source File  |  2001-02-05  |  15.0 KB  |  547 lines

  1. {*********************************************************}
  2. {* AALCS                                                 *}
  3. {* Copyright (c) Julian M Bucknall 2001                  *}
  4. {* All rights reserved.                                  *}
  5. {*********************************************************}
  6. {* Algorithms Alfresco: Longest Common Subsequence       *}
  7. {*********************************************************}
  8.  
  9. {Note: this unit is released as freeware. In other words, you are free
  10.        to use this unit in your own applications, however I retain all
  11.        copyright to the code. JMB}
  12.  
  13. unit AALCS;
  14.  
  15. {define this if you want to use the debug facilities}
  16. {$DEFINE Debug}
  17.  
  18. interface
  19.  
  20. uses
  21.   Windows, SysUtils, Classes;
  22.  
  23. type
  24.   TaaLCSDir = (
  25.      ldNorth,
  26.      ldNorthWest,
  27.      ldWest);
  28.  
  29.   PaaLCSData = ^TaaLCSData;
  30.   TaaLCSData = packed record
  31.     ldLen  : integer;
  32.     ldPrev : TaaLCSDir;
  33.   end;
  34.  
  35.   TaaLCSMatrix = class
  36.     private
  37.       FCols     : integer;
  38.       FMatrix   : TList;
  39.       FRows     : integer;
  40.     protected
  41.       function mxGetItem(aRow, aCol : integer) : PaaLCSData;
  42.       procedure mxSetItem(aRow, aCol : integer;
  43.                           aValue : PaaLCSData);
  44.     public
  45.       constructor Create(aRowCount, aColCount : integer);
  46.       destructor Destroy; override;
  47.  
  48.       procedure Clear;
  49.  
  50.       {$IFDEF Debug}
  51.       procedure Dump;
  52.       {$ENDIF}
  53.  
  54.       property Items[aRow, aCol : integer] : PaaLCSData
  55.                   read mxGetItem write mxSetItem; default;
  56.  
  57.       property RowCount : integer read FRows;
  58.       property ColCount : integer read FCols;
  59.   end;
  60.  
  61.   TaaStringLCS = class
  62.     private
  63.       FFromStr : string;
  64.       FMatrix  : TaaLCSMatrix;
  65.       FToStr   : string;
  66.     protected
  67.       procedure slFillMatrix;
  68.       function slGetCell(aFromInx, aToInx : integer) : integer;
  69.       procedure slWriteChange(var F : System.Text;
  70.                                   aFromInx, aToInx : integer);
  71.     public
  72.       constructor Create(const aFromStr, aToStr : string);
  73.       destructor Destroy; override;
  74.  
  75.       procedure WriteChanges(const aFileName : string);
  76.   end;
  77.  
  78.   TaaFileLCS = class
  79.     private
  80.       FFromFile : TStringList;
  81.       FMatrix   : TaaLCSMatrix;
  82.       FToFile   : TStringList;
  83.     protected
  84.       function slGetCell(aFromInx, aToInx : integer) : integer;
  85.       procedure slWriteChange(var F : System.Text;
  86.                                   aFromInx, aToInx : integer);
  87.     public
  88.       constructor Create(const aFromFile, aToFile : string);
  89.       destructor Destroy; override;
  90.  
  91.       procedure WriteChanges(const aFileName : string);
  92.   end;
  93.  
  94. implementation
  95.  
  96. {===TaaLCSMatrix=====================================================}
  97. constructor TaaLCSMatrix.Create(aRowCount, aColCount : integer);
  98. var
  99.   Row     : integer;
  100.   ColList : TList;
  101. begin
  102.   {create the ancestor}
  103.   inherited Create;
  104.  
  105.   {simple validation}
  106.   if (aRowCount <= 0) or (aColCount < 0) then
  107.     raise Exception.Create(
  108.                   'TaaLCSMatrix.Create: Invalid Row or column count');
  109.   FRows := aRowCount;
  110.   FCols := aColCount;
  111.  
  112.   {create the matrix: it'll be a TList of TLists in row order}
  113.   FMatrix := TList.Create;
  114.   FMatrix.Count := aRowCount;
  115.   for Row := 0 to pred(aRowCount) do begin
  116.     ColList := TList.Create;
  117.     ColList.Count := aColCount;
  118.     TList(FMatrix.List^[Row]) := ColList;
  119.   end;
  120. end;
  121. {--------}
  122. destructor TaaLCSMatrix.Destroy;
  123. var
  124.   Row : integer;
  125. begin
  126.   {destroy the matrix}
  127.   if (FMatrix <> nil) then begin
  128.     Clear;
  129.     for Row := 0 to pred(FRows) do
  130.       TList(FMatrix.List^[Row]).Free;
  131.     FMatrix.Free;
  132.   end;
  133.  
  134.   {destroy the ancestor}
  135.   inherited Destroy;
  136. end;
  137. {--------}
  138. procedure TaaLCSMatrix.Clear;
  139. var
  140.   Row, Col : integer;
  141.   ColList  : TList;
  142. begin
  143.   for Row := 0 to pred(FRows) do begin
  144.     ColList := TList(FMatrix.List^[Row]);
  145.     if (ColList <> nil) then
  146.       for Col := 0 to pred(FCols) do begin
  147.         Dispose(PaaLCSData(ColList.List^[Col]));
  148.         ColList.List^[Col] := nil;
  149.       end;
  150.   end;
  151. end;
  152. {--------}
  153. {$IFDEF Debug}
  154. procedure TaaLCSMatrix.Dump;
  155. var
  156.   Row, Col : integer;
  157.   LCSData  : PaaLCSData;
  158. begin
  159.   for Row := 0 to pred(FRows) do begin
  160.     for Col := 0 to pred(FCols) do begin
  161.       LCSData := Items[Row, Col];
  162.       if (LCSData = nil) then begin
  163.         write('  ? 0');
  164.       end
  165.       else begin
  166.         case LCSData^.ldPrev of
  167.           ldNorth     : write('  |');
  168.           ldNorthWest : write('  \');
  169.           ldWest      : write('  -');
  170.         end;
  171.         write(LCSData^.ldLen:2);
  172.       end;
  173.     end;
  174.     writeln;
  175.   end;
  176. end;
  177. {$ENDIF}
  178. {--------}
  179. function TaaLCSMatrix.mxGetItem(aRow, aCol : integer) : PaaLCSData;
  180. begin
  181.   if not ((0 <= aRow) and (aRow < RowCount) and
  182.           (0 <= aCol) and (aCol < ColCount)) then
  183.     raise Exception.Create(
  184.          'TaaLCSMatrix.mxGetItem: Row or column index out of bounds');
  185.   Result := PaaLCSData(TList(FMatrix.List^[aRow]).List^[aCol]);
  186. end;
  187. {--------}
  188. procedure TaaLCSMatrix.mxSetItem(aRow, aCol : integer;
  189.                     aValue : PaaLCSData);
  190. begin
  191.   if not ((0 <= aRow) and (aRow < RowCount) and
  192.           (0 <= aCol) and (aCol < ColCount)) then
  193.     raise Exception.Create(
  194.          'TaaLCSMatrix.mxSetItem: Row or column index out of bounds');
  195.   TList(FMatrix.List^[aRow]).List^[aCol] := aValue;
  196. end;
  197. {====================================================================}
  198.  
  199.  
  200. {===TaaStringLCS=====================================================}
  201. constructor TaaStringLCS.Create(const aFromStr, aToStr : string);
  202. begin
  203.   {create the ancestor}
  204.   inherited Create;
  205.  
  206.   {save the strings}
  207.   FFromStr := aFromStr;
  208.   FToStr := aToStr;
  209.  
  210.   {create the matrix}
  211.   FMatrix := TaaLCSMatrix.Create(succ(length(aFromStr)),
  212.                                  succ(length(aToStr)));
  213.  
  214.   {now fill in the matrix}
  215.   slGetCell(length(aFromStr), length(aToStr));
  216. //  slFillMatrix;
  217.  
  218.   {$IFDEF Debug}
  219.   writeln('Matrix for ', aFromStr, ' -> ', aToStr);
  220.   FMatrix.Dump;
  221.   {$ENDIF}
  222. end;
  223. {--------}
  224. destructor TaaStringLCS.Destroy;
  225. begin
  226.   {destroy the matrix}
  227.   FMatrix.Free;
  228.  
  229.   {destroy the ancestor}
  230.   inherited Destroy;
  231. end;
  232. {--------}
  233. procedure TaaStringLCS.slFillMatrix;
  234. var
  235.   FromInx : integer;
  236.   ToInx   : integer;
  237.   FromCh  : PAnsiChar;
  238.   ToCh    : PAnsiChar;
  239.   NorthLen: integer;
  240.   WestLen : integer;
  241.   LCSData : PaaLCSData;
  242. begin
  243.   {Create the empty items along the top and left sides}
  244.   for ToInx := 0 to length(FToStr) do begin
  245.     New(LCSData);
  246.     LCSData.ldLen := 0;
  247.     LCSData.ldPrev := ldWest;
  248.     FMatrix[0, ToInx] := LCSData;
  249.   end;
  250.   for FromInx := 1 to length(FFromStr) do begin
  251.     New(LCSData);
  252.     LCSData.ldLen := 0;
  253.     LCSData.ldPrev := ldNorth;
  254.     FMatrix[FromInx, 0] := LCSData;
  255.   end;
  256.  
  257.   {fill in the matrix, row by row, from left to right}
  258.   FromCh := PAnsiChar(FFromStr);
  259.   for FromInx := 1 to length(FFromStr) do begin
  260.     ToCh := PAnsiChar(FToStr);
  261.     for ToInx := 1 to length(FToStr) do begin
  262.       {create the new item}
  263.       New(LCSData);
  264.  
  265.       {if the two current chars are equal, increment the count
  266.       from the northwest, that's our previous item}
  267.       if (FromCh^ = ToCh^) then begin
  268.         LCSData^.ldPrev := ldNorthWest;
  269.         LCSData^.ldLen := succ(FMatrix[FromInx-1, ToInx-1]^.ldLen);
  270.       end
  271.  
  272.       {otherwise the current characters are different: use the
  273.       maximum of the north or west (west preferred}
  274.       else begin
  275.         NorthLen := FMatrix[FromInx-1, ToInx]^.ldLen;
  276.         WestLen := FMatrix[FromInx, ToInx-1]^.ldLen;
  277.         if (NorthLen > WestLen) then begin
  278.           LCSData^.ldPrev := ldNorth;
  279.           LCSData^.ldLen := NorthLen;
  280.         end
  281.         else begin
  282.           LCSData^.ldPrev := ldWest;
  283.           LCSData^.ldLen := WestLen;
  284.         end;
  285.       end;
  286.  
  287.       {set the item in the matrix}
  288.       FMatrix[FromInx, ToInx] := LCSData;
  289.  
  290.       {move one char on in the to string}
  291.       inc(ToCh);
  292.     end;
  293.  
  294.     {move one char on in the from string}
  295.     inc(FromCh);
  296.   end;
  297.   {at this point the item in the bottom right hand corner has
  298.    the length of the LCS and the calculation is complete}
  299. end;
  300. {--------}
  301. function TaaStringLCS.slGetCell(aFromInx, aToInx : integer) : integer;
  302. var
  303.   LCSData : PaaLCSData;
  304.   NorthLen: integer;
  305.   WestLen : integer;
  306. begin
  307.   if (aFromInx = 0) or (aToInx = 0) then
  308.     Result := 0
  309.   else begin
  310.     LCSData := FMatrix[aFromInx, aToInx];
  311.     if (LCSData <> nil) then
  312.       Result := LCSData^.ldLen
  313.     else begin
  314.       {create the new item}
  315.       New(LCSData);
  316.  
  317.       {if the two current chars are equal, increment the count
  318.       from the northwest, that's our previous item}
  319.       if (FFromStr[aFromInx] = FToStr[aToInx]) then begin
  320.         LCSData^.ldPrev := ldNorthWest;
  321.         LCSData^.ldLen := slGetCell(aFromInx-1, aToInx-1) + 1;
  322.       end
  323.  
  324.       {otherwise the current characters are different: use the
  325.       maximum of the north or west (west preferred}
  326.       else begin
  327.         NorthLen := slGetCell(aFromInx-1, aToInx);
  328.         WestLen := slGetCell(aFromInx, aToInx-1);
  329.         if (NorthLen > WestLen) then begin
  330.           LCSData^.ldPrev := ldNorth;
  331.           LCSData^.ldLen := NorthLen;
  332.         end
  333.         else begin
  334.           LCSData^.ldPrev := ldWest;
  335.           LCSData^.ldLen := WestLen;
  336.         end;
  337.       end;
  338.  
  339.       {set the item in the matrix}
  340.       FMatrix[aFromInx, aToInx] := LCSData;
  341.  
  342.       {return the length of this LCS}
  343.       Result := LCSData^.ldLen;
  344.     end;
  345.   end;
  346. end;
  347. {--------}
  348. procedure TaaStringLCS.slWriteChange(var F : System.Text;
  349.                                          aFromInx, aToInx : integer);
  350. var
  351.   Cell : PaaLCSData;
  352. begin
  353.   {if both indexes are zero, this is the first
  354.    cell of the LCS matrix, so just exit}
  355.   if (aFromInx = 0) and (aToInx = 0) then
  356.     Exit;
  357.  
  358.   {if the from index is zero, we're flush against the left
  359.    hand side of the matrix, so go up; this'll be a deletion}
  360.   if (aFromInx = 0) then begin
  361.     slWriteChange(F, aFromInx, aToInx-1);
  362.     writeln(F, '-> ', FToStr[aToInx]);
  363.   end
  364.   {if the to index is zero, we're flush against the top side
  365.    of the matrix, so go left; this'll be an insertion}
  366.   else if (aToInx = 0) then begin
  367.     slWriteChange(F, aFromInx-1, aToInx);
  368.     writeln(F, '<- ', FFromStr[aFromInx]);
  369.   end
  370.   {otherwise see what the cell says to do}
  371.   else begin
  372.     Cell := FMatrix[aFromInx, aToInx];
  373.     case Cell^.ldPrev of
  374.       ldNorth :
  375.         begin
  376.           slWriteChange(F, aFromInx-1, aToInx);
  377.           writeln(F, '<- ', FFromStr[aFromInx]);
  378.         end;
  379.       ldNorthWest :
  380.         begin
  381.           slWriteChange(F, aFromInx-1, aToInx-1);
  382.           writeln(F, '   ', FFromStr[aFromInx]);
  383.         end;
  384.       ldWest :
  385.         begin
  386.           slWriteChange(F, aFromInx, aToInx-1);
  387.           writeln(F, '-> ', FToStr[aToInx]);
  388.         end;
  389.     end;
  390.   end;
  391. end;
  392. {--------}
  393. procedure TaaStringLCS.WriteChanges(const aFileName : string);
  394. var
  395.   F : System.Text;
  396. begin
  397.   System.Assign(F, aFileName);
  398.   System.Rewrite(F);
  399.   try
  400.     slWriteChange(F, length(FFromStr), length(FToStr));
  401.   finally
  402.     System.Close(F);
  403.   end;
  404. end;
  405. {====================================================================}
  406.  
  407.  
  408. {===TaaFileLCS=====================================================}
  409. constructor TaaFileLCS.Create(const aFromFile, aToFile : string);
  410. begin
  411.   {create the ancestor}
  412.   inherited Create;
  413.  
  414.   {read the files}
  415.   FFromFile := TStringList.Create;
  416.   FFromFile.LoadFromFile(aFromFile);
  417.   FToFile := TStringList.Create;
  418.   FToFile.LoadFromFile(aToFile);
  419.  
  420.   {create the matrix}
  421.   FMatrix := TaaLCSMatrix.Create(FFromFile.Count, FToFile.Count);
  422.  
  423.   {now fill in the matrix}
  424.   slGetCell(pred(FFromFile.Count), pred(FToFile.Count));
  425. end;
  426. {--------}
  427. destructor TaaFileLCS.Destroy;
  428. begin
  429.   {destroy the matrix}
  430.   FMatrix.Free;
  431.  
  432.   {free the string lists}
  433.   FFromFile.Free;
  434.   FToFile.Free;
  435.  
  436.   {destroy the ancestor}
  437.   inherited Destroy;
  438. end;
  439. {--------}
  440. function TaaFileLCS.slGetCell(aFromInx, aToInx : integer) : integer;
  441. var
  442.   LCSData : PaaLCSData;
  443.   NorthLen: integer;
  444.   WestLen : integer;
  445. begin
  446.   if (aFromInx = -1) or (aToInx = -1) then
  447.     Result := 0
  448.   else begin
  449.     LCSData := FMatrix[aFromInx, aToInx];
  450.     if (LCSData <> nil) then
  451.       Result := LCSData^.ldLen
  452.     else begin
  453.       {create the new item}
  454.       New(LCSData);
  455.  
  456.       {if the two current lines are equal, increment the count
  457.       from the northwest, that's our previous item}
  458.       if (FFromFile[aFromInx] = FToFile[aToInx]) then begin
  459.         LCSData^.ldPrev := ldNorthWest;
  460.         LCSData^.ldLen := slGetCell(aFromInx-1, aToInx-1) + 1;
  461.       end
  462.  
  463.       {otherwise the current lines are different: use the
  464.       maximum of the north or west (west preferred}
  465.       else begin
  466.         NorthLen := slGetCell(aFromInx-1, aToInx);
  467.         WestLen := slGetCell(aFromInx, aToInx-1);
  468.         if (NorthLen > WestLen) then begin
  469.           LCSData^.ldPrev := ldNorth;
  470.           LCSData^.ldLen := NorthLen;
  471.         end
  472.         else begin
  473.           LCSData^.ldPrev := ldWest;
  474.           LCSData^.ldLen := WestLen;
  475.         end;
  476.       end;
  477.  
  478.       {set the item in the matrix}
  479.       FMatrix[aFromInx, aToInx] := LCSData;
  480.  
  481.       {return the length of this LCS}
  482.       Result := LCSData^.ldLen;
  483.     end;
  484.   end;
  485. end;
  486. {--------}
  487. procedure TaaFileLCS.slWriteChange(var F : System.Text;
  488.                                          aFromInx, aToInx : integer);
  489. var
  490.   Cell : PaaLCSData;
  491. begin
  492.   {if both indexes are less than zero, this is the first
  493.    cell of the LCS matrix, so just exit}
  494.   if (aFromInx = -1) and (aToInx = -1) then
  495.     Exit;
  496.  
  497.   {if the from index is less than zero, we're flush against the
  498.    left hand side of the matrix, so go up; this'll be a deletion}
  499.   if (aFromInx = -1) then begin
  500.     slWriteChange(F, aFromInx, aToInx-1);
  501.     writeln(F, '-> ', FToFile[aToInx]);
  502.   end
  503.   {if the to index is less than zero, we're flush against the
  504.    top side of the matrix, so go left; this'll be an insertion}
  505.   else if (aToInx = -1) then begin
  506.     slWriteChange(F, aFromInx-1, aToInx);
  507.     writeln(F, '<- ', FFromFile[aFromInx]);
  508.   end
  509.   {otherwise see what the cell says to do}
  510.   else begin
  511.     Cell := FMatrix[aFromInx, aToInx];
  512.     case Cell^.ldPrev of
  513.       ldNorth :
  514.         begin
  515.           slWriteChange(F, aFromInx-1, aToInx);
  516.           writeln(F, '<- ', FFromFile[aFromInx]);
  517.         end;
  518.       ldNorthWest :
  519.         begin
  520.           slWriteChange(F, aFromInx-1, aToInx-1);
  521.           writeln(F, '   ', FFromFile[aFromInx]);
  522.         end;
  523.       ldWest :
  524.         begin
  525.           slWriteChange(F, aFromInx, aToInx-1);
  526.           writeln(F, '-> ', FToFile[aToInx]);
  527.         end;
  528.     end;
  529.   end;
  530. end;
  531. {--------}
  532. procedure TaaFileLCS.WriteChanges(const aFileName : string);
  533. var
  534.   F : System.Text;
  535. begin
  536.   System.Assign(F, aFileName);
  537.   System.Rewrite(F);
  538.   try
  539.     slWriteChange(F, pred(FFromFile.Count), pred(FToFile.Count));
  540.   finally
  541.     System.Close(F);
  542.   end;
  543. end;
  544. {====================================================================}
  545.  
  546. end.
  547.